﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Kokomo.PeInspector.Reflection;
using System.Reflection;
using System.IO;
using Kokomo.PeInspector.Data;
using Kokomo.PeInspector.Reflection.Disassembly;
using Kokomo.Mirror.CliMetadata;

namespace Kokomo.PeInspector.Reflection
{
	class CachedDependencyIndex : DependencyIndex
	{
		public CachedDependencyIndex(
			AssemblyIndexCache cache,
			IAssemblyListHost host
			)
			: base(host)
		{
			if (cache == null) throw new ArgumentNullException("cache");

			this.Cache = cache;
		}

		public AssemblyIndexCache Cache { get; private set; }

		protected override void AddDependenciesToIndex(
			Assembly assembly
		)
		{
			Module mainModule = assembly.ManifestModule;
			AssemblyIndexCache cache = this.Cache;

			string fileName = mainModule.FullyQualifiedName;
			DateTime moduleDateTime = File.GetLastWriteTimeUtc(fileName);
			ModuleFile moduleFile = cache.QueryModuleByFileName(fileName);
			if (
				(moduleFile == null)
				//|| (DateTime. moduleFile.ModuleDateTime..Equals(moduleDateTime))
				)
			{
				int moduleID = cache.RegisterModule(mainModule);

				cache.CacheDependencies(
					this.CalculateDependencies(assembly)
					);
			}
		}

		private Module ResolveModule(int moduleID)
		{
			ModuleFile file = this.Cache.QueryModuleByID(moduleID);
			if (file == null) return null;

			Assembly assembly = this.Host.LoadContext.LoadAssemblyFile(file.FileName);
			return assembly.ManifestModule;
		}

		private MsilMemberDependency Resolve(
			Module dependencyModule,
			MemberDependency dependencyRecord
			)
		{
			int usage = dependencyRecord.Usage;

			MsilMemberDependency dependency = null;
			ModuleMemberReference member = ModuleMemberReference.FromModuleToken(dependencyModule, dependencyRecord.DependencyToken);
			MemberTypes memberType = (MemberTypes)dependencyRecord.MemberType;
			switch (memberType)
			{
				case MemberTypes.TypeInfo:
					dependency = MsilTypeDependency.FromTypeRef(
						member,
						(MsilTypeUsage)dependencyRecord.Usage,
						dependencyRecord.Offset
						);
					break;
				case MemberTypes.Method:
					dependency = MsilMethodDependency.FromMethodRef(
						member,
						(MsilMethodUsage)dependencyRecord.Usage,
						dependencyRecord.Offset
						);
					break;
				case MemberTypes.Field:
					dependency = MsilFieldDependency.FromFieldRef(
						member,
						(MsilVariableReferenceType)dependencyRecord.Usage,
						dependencyRecord.Offset
						);
					break;
			}

			return dependency;
		}

		public override IEnumerable<MsilDependency> GetDependenciesOf(
			ModuleMemberReference dependentMember,
			ProgressInfo progress
			)
		{
			int moduleID = this.Cache.GetModuleID(dependentMember.Module);
			List<MsilDependency> dependencies = new List<MsilDependency>();
			lock (this.Cache)
			{
				IEnumerable<IGrouping<int, MemberDependency>> dependencyRecordGroupings = this.Cache.QueryDependenciesOf(moduleID, dependentMember.MetadataToken).GroupBy(d => d.DependencyModuleId);

				foreach (var dependencyRecordGrouping in dependencyRecordGroupings)
				{
					Module dependencyModule = this.ResolveModule(dependencyRecordGrouping.Key);
					if (dependencyModule != null)
					{
						foreach (var dependencyRecord in dependencyRecordGrouping)
						{
							MsilDependency dependency = this.Resolve(dependencyModule, dependencyRecord);
							if (dependency != null)
								dependencies.Add(dependency);
						}
					}
				}
			}

			return dependencies;
		}
		public override IEnumerable<IGrouping<ModuleMemberReference, MsilMemberDependency>> GetDependentsOf(
			ModuleMemberReference dependencyMember,
			ProgressInfo progress
			)
		{
			ModuleFile dependencyModuleRecord = this.Cache.QueryModuleByFileName(dependencyMember.Module.FullyQualifiedName);
			if (dependencyModuleRecord == null)
				return new IGrouping<ModuleMemberReference, MsilMemberDependency>[0];

			List<IGrouping<ModuleMemberReference, MsilMemberDependency>> dependencyGroups = new List<IGrouping<ModuleMemberReference, MsilMemberDependency>>();
			lock (this.Cache)
			{
				IEnumerable<IGrouping<int, MemberDependency>> dependencyModuleGroupings = this.Cache.QueryDependentsOf(dependencyModuleRecord.Id, dependencyMember.MetadataToken).GroupBy(d => d.DependentModuleId);

				foreach (var dependencyModuleGrouping in dependencyModuleGroupings)
				{
					ModuleFile moduleFile = this.Cache.QueryModuleByID(dependencyModuleGrouping.Key);
					Assembly dependentAssembly = this.Host.LoadContext.LoadAssemblyFile(moduleFile.FileName);

					IEnumerable<IGrouping<int, MemberDependency>> dependencyMemberGroupings = dependencyModuleGrouping.GroupBy(d => d.DependentToken);

					foreach (var dependencyRecordGrouping in dependencyMemberGroupings)
					{
						MemberInfo dependentMember = dependentAssembly.ManifestModule.ResolveMember(dependencyRecordGrouping.Key);
						ModuleMemberReference dependentMemberRef = new ModuleMemberReference(dependentMember);

						List<MsilMemberDependency> dependencies = new List<MsilMemberDependency>();

						foreach (var dependencyRecord in dependencyRecordGrouping)
						{
							MsilMemberDependency dependency = this.Resolve(dependencyMember.Module, dependencyRecord);
							if (dependency != null)
								dependencies.Add(dependency);
						}
						dependencyGroups.Add(new Grouping<ModuleMemberReference, MsilMemberDependency>(dependentMemberRef, dependencies));
					}
				}
			}

			return dependencyGroups;
		}
	}
}
